package game_logic.control.ai;

import game_logic.model.*;
import game_logic.control.MoveController;
import game_logic.control.GameController;
import game_logic.util.Vec2;

import java.util.ArrayList;
import java.util.Map;

/**
 * 搜索model
 */
public class SearchModel {
    private static int DEPTH = 2;
    private Board board;
    private final MoveController controller;

    public SearchModel(){
        this.controller = new MoveController();
    }

    public AlphaBetaNode search(Board board) {
        this.board = board;
        if (board.getPieceSize() < 28)
            DEPTH = 3;
        if (board.getPieceSize() < 16)
            DEPTH = 4;
        if (board.getPieceSize() < 6)
            DEPTH = 5;
        if (board.getPieceSize() < 4)
            DEPTH = 6;
        long startTime = System.currentTimeMillis();
        AlphaBetaNode best = null;
        ArrayList<AlphaBetaNode> moves = generateMovesForAll(true);
        for (AlphaBetaNode n : moves) {
            /* Move*/
            Piece eaten = board.getPiece(n.to[0],n.to[1]);      // 获取可能被吃的棋子
            board.setPiece(n.to[0],n.to[1],n.piece);
            board.setPiece(n.from[0],n.from[1],null);
            n.value = alphaBeta(DEPTH, Integer.MIN_VALUE, Integer.MAX_VALUE, false);

            if (best == null || n.value >= best.value)
                best = n;
            /* Back move*/
            board.setPiece(n.from[0],n.from[1],n.piece);
            board.setPiece(n.to[0], n.to[1],eaten);
        }
        long finishTime = System.currentTimeMillis();
        System.out.println(finishTime - startTime);
        return best;
    }


    private int alphaBeta(int depth, int alpha, int beta, boolean isMax) {
        /* Return evaluation if reaching leaf node or any side won.*/
        if (depth == 0 || board.getWinner() != null)
            return new EvalModel().eval(board, 'b');
        ArrayList<AlphaBetaNode> moves = generateMovesForAll(isMax);

        synchronized (this) {
            for (final AlphaBetaNode n : moves) {
                Piece eaten = board.getPiece(n.to[0],n.to[1]);      // 获取可能被吃的棋子
                board.setPiece(n.to[0],n.to[1],n.piece);
                board.setPiece(n.from[0],n.from[1],null);
            /* Is maximizing player? */
                final int finalBeta = beta;
                final int finalAlpha = alpha;
                final int finalDepth = depth;
                final int[] temp = new int[1];
                /* Only adopt multi threading strategy in depth 2. To avoid conjunction.*/
                if (depth == 2) {
                    if (isMax) {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                temp[0] = Math.max(finalAlpha, alphaBeta(finalDepth - 1, finalAlpha, finalBeta, false));
                            }
                        }).run();
                        alpha = temp[0];
                    } else {
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                temp[0] = Math.min(finalBeta, alphaBeta(finalDepth - 1, finalAlpha, finalBeta, true));
                            }
                        }).run();
                        beta = temp[0];
                    }
                }
                else {
                    if (isMax) alpha = Math.max(alpha, alphaBeta(depth - 1, alpha, beta, false));
                    else beta = Math.min(beta, alphaBeta(depth - 1, alpha, beta, true));
                }
                /* Back move*/
                board.setPiece(n.from[0],n.from[1],n.piece);
                board.setPiece(n.to[0], n.to[1],eaten);
            /* Cut-off */
                if (beta <= alpha) break;
            }
        }
        return isMax ? alpha : beta;
    }

    private ArrayList<AlphaBetaNode> generateMovesForAll(boolean isMax) {
        ArrayList<AlphaBetaNode> moves = new ArrayList<>();
        for (int x = 0; x < 9; ++x) {
            for (int y = 0; y < 10; ++y) {
                Piece piece = this.board.getPiece(x, y);
                if (piece==null)
                    continue;

                if (isMax && piece.getOwner() == 0) continue;
                if (!isMax && piece.getOwner() == 1) continue;

                for (Vec2 vec : controller.getMovablePosition(board,new Vec2(x,y)))
                    moves.add(new AlphaBetaNode(piece, new int[]{x,y},new int[]{vec.x, vec.y}));
            }
        }
        return moves;
    }

}